﻿using System;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;

namespace CSharp5
{
    public class CodeDom
    {
        public static void Start()
        {
            // Create a method on the fly writing on the console
            var dynMeth = new DynamicMethod("Foo", null, null, typeof (CodeDom));

            ILGenerator gen = dynMeth.GetILGenerator();
            gen.EmitWriteLine("Hello World");
            gen.Emit(OpCodes.Ret);

            dynMeth.Invoke(null, null);

            // Create a method on the fly that call a private method
            dynMeth = new DynamicMethod("Foo", null, null, typeof(CodeDom));
            gen = dynMeth.GetILGenerator();

            MethodInfo privateMethod = typeof (CodeDom).GetMethod("PrivateMethod", BindingFlags.Static | BindingFlags.NonPublic);

            gen.Emit(OpCodes.Call, privateMethod);
            gen.Emit(OpCodes.Ret);
            dynMeth.Invoke(null, null);

            // Create a method on the fly with parameters
            dynMeth = new DynamicMethod("Foo", typeof(int), new[] { typeof(int), typeof(int) }, typeof(CodeDom));

            gen = dynMeth.GetILGenerator();
            gen.Emit(OpCodes.Ldarg_0);
            gen.Emit(OpCodes.Ldarg_1);
            gen.Emit(OpCodes.Add);
            gen.Emit(OpCodes.Ret);

            int res = (int)dynMeth.Invoke(null, new object[] { 5, 4 });
            Console.WriteLine("\nres = {0}", res);

            // Create a delegate dynamically and then use as a filter
            int[] nums = new [] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

            dynMeth = new DynamicMethod("GreaterThenFive", typeof(bool), new[] { typeof(int) }, typeof(CodeDom));

            gen = dynMeth.GetILGenerator();
            gen.Emit(OpCodes.Ldarg_0);
            gen.Emit(OpCodes.Ldc_I4, 5);
            gen.Emit(OpCodes.Cgt);
            gen.Emit(OpCodes.Ret);
            
            var greaterThenFive = (Func<int, bool>) dynMeth.CreateDelegate(typeof(Func<int, bool>));

            Console.WriteLine("\nNumbers greater than five:");
            foreach (var num in nums.Where(greaterThenFive))
            {
                Console.WriteLine("num = {0}", num);
            }

            // Full CodeDom API

            AppDomain appDomain = AppDomain.CurrentDomain;

            AssemblyName aname = new AssemblyName("MyDynamicAssembly");
            AssemblyBuilder aBuild = appDomain.DefineDynamicAssembly(aname, AssemblyBuilderAccess.Run);
            ModuleBuilder modBuilder = aBuild.DefineDynamicModule("DynModule");
            TypeBuilder tb = modBuilder.DefineType("Student", TypeAttributes.Public);
            MethodBuilder mb = tb.DefineMethod("Write", MethodAttributes.Public);
            gen = mb.GetILGenerator();

            gen.EmitWriteLine("Called Write method from student");
            gen.Emit(OpCodes.Ret);

            var studentType = tb.CreateType();

            Console.WriteLine("StudentType: {0}", studentType.FullName);

            var student = Activator.CreateInstance(studentType);
            studentType.GetMethod("Write").Invoke(student, null);
        }        

        private static void PrivateMethod()
        {
            Console.Write("This is a private method called using codeDom");
        }
    }
}